package com.rigsofrods.truckeditor.rig;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.fixedfunc.GLLightingFunc;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import javax.media.opengl.glu.GLU;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;


import com.jogamp.opengl.util.gl2.GLUT;
import com.rigsofrods.truckeditor.geometry.BoundingBox3D;
import com.rigsofrods.truckeditor.geometry.Mesh;
import com.rigsofrods.truckeditor.geometry.Object3D;
import com.rigsofrods.truckeditor.geometry.Mesh.MeshElementType;
import com.rigsofrods.truckeditor.transforms3D.Vec3D;

/**TODO {@link TruckModel} will observe changes in tree structure and will acquire data from it itself.
 * Viewports should listen to changes in this model. 
 * This will of course work even during loading the file. */ 
public class TruckModel extends Object3D implements TreeModelListener{
	//Geometry
	private List<Node> nodes = new ArrayList<Node>();
	private Map<String, Node> nodes2 = new TreeMap<String, Node>();
	private List<Beam> beams = new ArrayList<Beam>();
	//private Collection<Rig> rigs = new ArrayList<Rig>();
	private Vec3D centerOfGravity;
	private BoundingBox3D boundingBox = new BoundingBox3D();
	
	private FloatBuffer vb;
	private FloatBuffer lightParams;
	private float model[] = new float[16];
	private float proj[] = new float[16];
	private int view[] = new int[4];
	
	private Mesh beamMesh;
	private GLUT glut = new GLUT();
	private GLU glu = new GLU();
	
	public TruckModel(String objectName) {
		this(objectName, true);
	}	
	
	public TruckModel(String objectName, boolean sellectable) {
		super(objectName, sellectable);
		lightParams = FloatBuffer.allocate(8);
	}

	public void addNode(Node node) {
		/*
		if (nodes.isEmpty()){
			centerOfGravity = node.position;
		}else{
			updateCenterOfGravity(node.position, 1);
		}*/
		if(node.id >= 0){
			/*if(nodes.size() != node.id){
				throw new IllegalArgumentException("Node numbers must be consecutive!");
			}*/
			nodes.add(node);
		}else
			nodes2.put(node.name, node);
		boundingBox.includePoint(node.x, node.y, node.z);
	}

	public void connectNodes(String a, String b, String options) {
		Node n1,n2;
		n1 = getNode(a);
		n2 = getNode(b);
		if( n1 != null && n2 != null)
			beams.add(new Beam(n1, n2, beams.size(), options));
	}

	/*public void addRig(Rig rig) {
		rigs.add(rig);
	}
	
	public void addContacter(int a,int b, int c) {
		contacters.add(new Contacter(a, b, c));
	}*/

	/*public void recalculateCenterOfGravity() {
		if (nodes.isEmpty()){
			centerOfGravity = new Vec3D();
		}else {
			for (Node vertex : nodes) {
				centerOfGravity.add(vertex.position);
			}
			centerOfGravity = centerOfGravity.mul(1.0/nodes.size());
		}
	}*/

	public int getNodeCount(){
		return nodes.size() + nodes2.size();
	}
	public int getBeamCount(){
		return beams.size();
	}
	
	public Vec3D getCOG() {
		return centerOfGravity;
	}

	public BoundingBox3D getBoundingBox() {
		return boundingBox;
	}

	public Node getNode(String identifier) {
		Node n = nodes2.get(identifier);
		if (n == null) {
			try {
				int id = Integer.parseInt(identifier);
				n = nodes.get(id);
			} catch (IndexOutOfBoundsException e) {
				return null;
			} catch (NumberFormatException e) {
				return null;
			}
		}
		return n;
	}

	public void draw(GLAutoDrawable glDrawable) {
		final GL2 gl = glDrawable.getGL().getGL2();

		//Draw nodes
		//gl.glBegin(gl.GL_TRIANGLES);
		gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
		gl.glPushMatrix();
		gl.glLoadIdentity();
		lightParams.rewind();
		lightParams.put(0.2f).put(0.2f).put(0.2f).put(1f);//Ambient color
		lightParams.put(0).put(0).put(1).put(0).rewind();//Light direction
		gl.glLightfv(GLLightingFunc.GL_LIGHT0, GLLightingFunc.GL_AMBIENT, lightParams );
		//gl.glLightfv(GLLightingFunc.GL_LIGHT0, GLLightingFunc.GL_SPECULAR, lscol);
		gl.glLightfv(GLLightingFunc.GL_LIGHT0, GLLightingFunc.GL_POSITION, (FloatBuffer) lightParams.position(4));
		gl.glPopMatrix();
		gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
		gl.glPopMatrix();
		gl.glEnable(GLLightingFunc.GL_LIGHT0);
		gl.glEnable(GLLightingFunc.GL_LIGHTING);
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);

        // Set material properties.
		float matCol[] = new float[]{0.0f, 0.7f, 0.7f, 1f};
        gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_AMBIENT, matCol, 0);
        gl.glMaterialfv(GL.GL_FRONT, GLLightingFunc.GL_DIFFUSE, matCol, 0);
        //gl.glMaterialf(GL.GL_FRONT, GLLightingFunc.GL_SHININESS, 0.3f);
		for (Node n : nodes) {
			gl.glPushMatrix();
			gl.glTranslated(n.x, n.y, n.z);
			glut.glutSolidSphere(0.015, 8, 4);
			gl.glPopMatrix();
		}
		for (Node n : nodes2.values()) {
			gl.glPushMatrix();
			gl.glTranslated(n.x, n.y, n.z);
			glut.glutSolidSphere(0.015, 8, 4);
			gl.glPopMatrix();
		}
		gl.glDisable(GLLightingFunc.GL_LIGHTING);
		
		//Buffer will probably have to be recreated very often anyway (when this becomes an editor). In that case it could be allocated larger. 
		if(true && beamMesh == null){
			if (vb == null || vb.capacity() < beams.size() * 2 * 3){
				vb = FloatBuffer.allocate(beams.size() * 2 * 3);
			}else{
				vb.clear();
			}
			//TODO Use index buffer to copy much less nodes.
			for (int i = 0; i < beams.size(); i++) {
				vb.put((float) beams.get(i).a.x);
				vb.put((float) beams.get(i).a.y);
				vb.put((float) beams.get(i).a.z);
				vb.put((float) beams.get(i).b.x);
				vb.put((float) beams.get(i).b.y);
				vb.put((float) beams.get(i).b.z);
			}
			vb.rewind();
			beamMesh = new Mesh(vb, null, MeshElementType.ME_LINE);
		}
		gl.glColor3f(0.7f, 0.7f, 0.7f);
		beamMesh.draw(gl);
		
		//Node names
		if(true){
			gl.glGetFloatv(GLMatrixFunc.GL_MODELVIEW_MATRIX, model, 0);
			gl.glGetFloatv(GLMatrixFunc.GL_PROJECTION_MATRIX, proj, 0);
			gl.glGetIntegerv(GL.GL_VIEWPORT, view, 0);

			gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
			gl.glPushMatrix();
			gl.glLoadIdentity();
			gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
			gl.glPushMatrix();
			gl.glLoadIdentity();

			gl.glViewport(0, 0, glDrawable.getWidth(), glDrawable.getHeight());
			gl.glPushAttrib(GL2.GL_ENABLE_BIT);
			gl.glDisable(GLLightingFunc.GL_LIGHTING);
			gl.glDisable(GL.GL_DEPTH_TEST);

			int[] viewPort = new int[4];
			gl.glGetIntegerv(GL.GL_VIEWPORT, viewPort, 0);
			glu.gluOrtho2D(0, viewPort[2], viewPort[3], 0);
			
			gl.glColor3f(1f, 1f, 0.2f);
			
			float[] winPos = new float[4];
			for (Node n : nodes2.values()) {
				glu.gluProject((float)n.x, (float)n.y, (float)n.z, model,0, proj,0, view,0, winPos,0);
				if (winPos[2] > 0.f && winPos[2] < 1.f){
					gl.glRasterPos2i((int)winPos[0] + 1, view[3] - (int)winPos[1] - 2);
					glut.glutBitmapString(GLUT.BITMAP_HELVETICA_12, n.getName());
				}
			}
	        for (Node n : nodes) {
	        	glu.gluProject((float)n.x, (float)n.y, (float)n.z, model,0, proj,0, view,0, winPos,0);
				if (winPos[2] > 0.f && winPos[2] < 1.f){
					gl.glRasterPos2i((int)winPos[0] + 1, view[3] - (int)winPos[1] - 2);
					glut.glutBitmapString(GLUT.BITMAP_HELVETICA_12, n.getName());
				}
			}
	        /*gl.glRasterPos2i(10,10);
			StringBuilder sb = new StringBuilder();
			sb.append(view.get(0));
			sb.append(", ").append(view.get(1));
			sb.append(", ").append(view.get(2));
			sb.append(", ").append(view.get(3));
			sb.append(" | ").append(winPos.get(0));
			sb.append(", ").append(winPos.get(1));
			sb.append(", ").append(winPos.get(2));
			//sb.append(" | ").append(ok);
			glut.glutBitmapString(GLUT.BITMAP_HELVETICA_12, sb.toString());
			System.out.println(sb.toString());*/
			gl.glPopAttrib();
			gl.glPopMatrix();
			gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
			gl.glPopMatrix();
		}
	}

	@Override
	public void treeNodesChanged(TreeModelEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void treeNodesInserted(TreeModelEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void treeNodesRemoved(TreeModelEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void treeStructureChanged(TreeModelEvent e) {
		// TODO Auto-generated method stub
		
	}
}